#region Copyright (c) 2007-2008, Chris Klochek

/********************************************************************************************************************
'
' Copyright (c) 2007-2008, Chris Klochek
' All rights reserved.
' 
' Redistribution and use in source and binary forms, with or without modification, are permitted provided
' that the following conditions are met:
' 
' * Redistributions of source code must retain the above copyright notice, this list of conditions and the
' 	following disclaimer.
' 
' * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
' 	the following disclaimer in the documentation and/or other materials provided with the distribution.
' 
' * Neither the name of the author nor the names of its contributors may be used to endorse or 
' 	promote products derived from this software without specific prior written permission.
' 
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
' WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
' PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
' ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
' LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
' INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
' OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
' IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'
'*******************************************************************************************************************/

#endregion

using System;
using System.Collections.Generic;
using System.Text;
using SuedeTest.Core;
using System.Threading;
using System.Reflection;
using System.IO;
using System.Windows.Forms;

using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
using SuedeTest.Core.Actions;

namespace SuedeTest.ProcessHost {

   public class Helper {
      protected const string UI_THREAD_CREATION_EVENT = "OnUICreate";
      protected ProcessLink _procLink;

      protected virtual void InitOnUIThread() { }

      protected virtual void DisposeOnUIThread() { }


      protected virtual void InitOnMainThread() { }

      protected virtual int RunOnMainThread() { return 0; }

      protected virtual void DisposeOnMainThread() { }

      /// <summary>
      /// Loads additional assemblies for custom controls or other addtional controls that user wants to add
      /// </summary>
      /// <param name="additionalAssembliesDir"></param>
      protected void LoadAdditionalAssemblies(string additionalAssembliesDir) {
         if (!string.IsNullOrEmpty(additionalAssembliesDir) && Directory.Exists(additionalAssembliesDir)) {
            DirectoryInfo di = new DirectoryInfo(additionalAssembliesDir);

            List<string> loadedAsms = CreateAsmNameList(AppDomain.CurrentDomain.GetAssemblies());

            foreach (FileInfo fi in di.GetFiles()) {
               if (fi.Name.ToLower().EndsWith(".dll") && !loadedAsms.Contains(fi.Name.Substring(0, fi.Name.LastIndexOf('.')))) {
                  try {
                     Assembly.LoadFrom(fi.FullName);
                  } catch (Exception) {
                     Program.Log(fi.FullName + " not loaded");
                  }
               }
            }
         }
      }

      /// <summary>
      /// Create a list of assembly names, from the a list of assemblies
      /// </summary>
      /// <param name="loadedAsms"></param>
      /// <returns></returns>
      protected List<string> CreateAsmNameList(Assembly[] loadedAsms) {
         List<string> result = new List<string>();

         foreach (Assembly asm in loadedAsms) {
            result.Add(asm.FullName.Substring(0, asm.FullName.IndexOf(",")));
         }

         return result;
      }

      /// <summary>
      /// Callback to be called when a new UI thread is created
      /// </summary>
      /// <param name="threadID"></param>
      protected void OnNewUIThreadCreation(int threadID) {
         SuedeTest.Core.FormRepository.InitFormRepositoryOnThread();
      }

      public int Run(string fullPath, ProcessLink procLink) {
         return Run(fullPath, "", procLink);
      }

      public int Run(string fullPath, string searchPath, ProcessLink procLink) {

         Thread thread = new Thread(new ThreadStart(delegate()
         {
            InitOnMainThread();

            SuedeTest.Core.FormRepository.InitFormRepositoryOnThread();

            try {

               Assembly asm = Assembly.LoadFrom(fullPath, null);
               //check to see if the app is set up to inform us of UI creation events
               EventInfo ei = asm.EntryPoint.DeclaringType.GetEvent(UI_THREAD_CREATION_EVENT);
               if (ei != null) {
                  ei.AddEventHandler(null, Delegate.CreateDelegate(ei.EventHandlerType, this, "OnNewUIThreadCreation"));
               }

               //Logger.InitLogger("recorder.log");

               asm.EntryPoint.Invoke(null, new object[] { new string[] { } });
            } catch (Exception ex) {
               string errMsg = ex.InnerException == null ? ex.Message : ex.Message + "\n" + ex.InnerException.Message;
               _procLink.UpdateLog("Error executing process assembly entry point: " + errMsg);
            } finally {

               SuedeTest.Core.FormRepository.CloseRepositoryOnThread();

               DisposeOnUIThread();
            }

            //Logger.Log.Dispose();
         }));

         _procLink = procLink;

         LoadAdditionalAssemblies(searchPath);

         InitOnUIThread();

         thread.SetApartmentState(ApartmentState.STA);
         thread.IsBackground = true;
         thread.Start();

         int result = RunOnMainThread();

         _procLink.UpdateLog("Done running tests." + Environment.NewLine);
         DisposeOnMainThread();

         return (result);
      }
   }

   public class RunnerHelper : Helper {
      private string _testFileName;
      private string _specFileName;
      private List<string> _testNames;

      public RunnerHelper(string testFileName, string specFileName, string testNames)
         : this(testFileName, testNames) {
         _specFileName = specFileName;
      }

      public RunnerHelper(string testFileName, string testNames) {
         _testFileName = testFileName;
         _testNames = new List<string>(testNames.Split(' '));
      }

      protected override int RunOnMainThread() {
         ActionDelegateRegistry adr = new ActionDelegateRegistry();

         //Get the TestFrame using HTML parser from the test file
         TestProgram tp;

         if (string.IsNullOrEmpty(_specFileName)) {
            tp = new TestProgram(_testFileName, adr);
         } else {
            tp = new TestProgram(_testFileName, _specFileName, adr);
         }

         TestProgram.TestOutputChanged += new EventHandler(Program_TestOutputChanged);

         //if a setup-frame exists, run it.
         if (_testNames.Contains("SETUP") && tp.GetTestFrame("SETUP") != null) {
            if (!tp.GetTestFrame("SETUP").RunScript()) {
               //we didn't succeed, so return;
               TestProgram.TestOutputChanged -= Program_TestOutputChanged;
               return 1;
            }
         }

         int numFail = 0;
         foreach (string testName in _testNames) {
            if (testName != "SETUP" && tp.GetTestFrame(testName) != null) {
               try {
                  numFail = numFail + (tp.GetTestFrame(testName).RunScript() ? 0 : 1);
               } catch (Exception e) {
                  _procLink.UpdateLog(e.Message.ToString() + (e.InnerException == null ? "" : e.InnerException.ToString()));
               }
            }

         }

         TestProgram.TestOutputChanged -= Program_TestOutputChanged;

         return numFail;
      }

      void Program_TestOutputChanged(object sender, EventArgs e) {
         _procLink.UpdateLog((string)sender);
      }

      protected override void DisposeOnUIThread() {
         System.Diagnostics.Process.GetCurrentProcess().Kill();
      }
   }

   public class RecorderHelper : Helper {

      private Listener _listener;
      private FormsExplorer.FormExplorer _formExplorer;

      protected override void DisposeOnUIThread() {
         //TODO: sketchy--need to ensure _formExplorer even exists before closing it
         if (_formExplorer != null && !_formExplorer.IsDisposed) {
            _formExplorer.Invoke((MethodInvoker)delegate()
            {
               _formExplorer.Close();
            });
         }
      }

      protected override void InitOnUIThread() {
         _listener = new Listener();
         _listener.EventRecorded += new EventRecordedEvent(EventRecorded);
         _listener.LoggingOccured += new LoggingEventDelegate(_listener_LoggingOccured);

         _listener.Init();
      }

      private void _listener_LoggingOccured(string s) {
         _procLink.UpdateLog(s);
      }

      private void EventRecorded(Action action) {
         _procLink.AddTestInstruction(MakeSerializedAction(action));
      }

      private SerializedAction MakeSerializedAction(Action action) {
         if (action is EventAction) {
            return new SerializedAction(action as EventAction);
         } else if (action is ListViewItemWatchAction) {
            return new SerializedAction(action as ListViewItemWatchAction);
         } else if (action is PropertyAssertAction) {
            return new SerializedAction(action as PropertyAssertAction);
         }
         throw new Exception("I have never seen this action type before: " + action.GetType().ToString());
      }

      protected override int RunOnMainThread() {
         _formExplorer = new FormsExplorer.FormExplorer();
         _formExplorer.ActionAdded += new FormsExplorer.FormExplorer.ActionAddedHandler(FormExplorer_ActionAdded);
         _formExplorer.SaveTestClicked += new EventHandler(_formExplorer_SaveTestClicked);
         _formExplorer.RecordPauseClicked += new EventHandler(_formExplorer_RecordPauseClicked);
         _formExplorer.ClearTestClicked += new EventHandler(_formExplorer_ClearTestClicked);
         
         Application.Run(_formExplorer);

         return 0;
      }

      void _formExplorer_ClearTestClicked(object sender, EventArgs e) {
         _listener.ClearTest();
         _procLink.ClearTest();
      }

      void _formExplorer_RecordPauseClicked(object sender, EventArgs e) {
         if (_listener.IsRecording) {
            _listener.StopRecording();
         } else {
            _listener.StartRecording();
         }
      }

      void _formExplorer_SaveTestClicked(object sender, EventArgs e) {
         TestWriter tw = new TestWriter_FIT();

         if (!string.IsNullOrEmpty(_formExplorer.TestName)) {
            SaveFileDialog saveDialog = new SaveFileDialog();
            saveDialog.DefaultExt = ".html";
            saveDialog.OverwritePrompt = false;

            if (saveDialog.ShowDialog() == DialogResult.OK) {
               tw.TestName = _formExplorer.TestName;
               foreach (Action a in _listener.TestActions) {
                  tw.AddAction(MakeSerializedAction (a));
               }

               File.AppendAllText(saveDialog.FileName, tw.Test);
            }

         } else {
            MessageBox.Show("Please provide a test name.");
         }
      }

      void FormExplorer_ActionAdded(Action action) {
         _listener.AddEvent(action);
      }
   }
}